ECat Unity SDK使用文档#
一、接入步骤#
1.1 SDK集成#
双击SDK文件ECat_*.unitypackage,将文件导入到Unity工程
注意:
只导入需要的文件,如:
不使用xLua或tolua,则不导入libECatLuaTrace.so
不使用鸿蒙平台SDK,则不导入Harmony目录
每次更新SDK时,要覆盖旧版本的全部同名文件
1.2 SDK文件说明#
- ECat.cs:C#与各平台SDK的桥接文件
- ECatEditorSDK.cs:针对Unity Editor的SDK(在Editor中运行调试时,支持对C#异常和unity log的上传功能)
- ECatRegex.cs:支持对上传的unity log设置自定义的正则过滤表达式,以过滤log中的无效信息(如时间戳等)
- iOS
- libECat.a:iOS SDK
- Android
- libs
- ECat.jar:Java层异常捕获和异常管理上传SDK
- NDK:包含arm64-v8a/armeabi-v7a/x86/x86_64架构的so,其中:
- libECat.so:Native层异常捕获SDK
- libECatLuaTrace.so:Native崩溃时获取lua栈(若没有使用xlua或tolua可不导入)
- libs
- Harmony
- ecat.har:SDK静态库
- ECatAgent.etslib:ArkTs插件
- Windows
- x86_64
- ECat.dll:SDK
- ECatHandler.dll:进程外dump程序
- x86
- ECat.dll:SDK
- ECatHandler.dll:进程外dump程序
- ECatHandler.hpp:用于捕获InvalidParameter和PureCall运行时异常
- x86_64
- MiniGame
- ECat.unity.jslib:小游戏SDK(当前仅支持导出团结引擎微信小游戏时使用)
1.3 AndroidManifest.xml配置(仅安卓)#
打开Unity工程下的Assets/Plugins/Android/AndroidManifest.xml,添加以下权限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--若启用内存泄漏检测,需要增加如下配置/-->
<application
android:extractNativeLibs="true">
</application>
1.4 初始化#
1.4.1 初始化配置类和调用初始化函数#
建议在较早的场景中初始化
class MyClass
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnRuntimeMethodLoad()
{
#if UNITY_EDITOR
const string AppID = "平台分配的Unity Editor AppId";
#elif UNITY_ANDROID
const string AppID = "平台分配的安卓AppId";
#elif UNITY_IPHONE || UNITY_IOS
const string AppID = "平台分配的iOS AppId";
#elif UNITY_STANDALONE_WIN
const string AppID = "平台分配的Windows AppId";
#elif UNITY_OPENHARMONY
const string AppID = "平台分配的鸿蒙AppId";
#elif UNITY_MINIGAME
const string AppID = "平台分配的小游戏AppId";
#endif
ECat.Config config = new ECat.Config();
//config.enableDebugMode = true; //是否启用调试模式(默认:false)
config.appVersion = "2.0"; //应用版本
config.appChannel = "JJ"; //渠道
config.userId = "user2"; //用户id
ECat.InitECat(AppID, config);
}
}
1.4.2 网络权限设置(仅安卓、鸿蒙)#
特别注意:为合规性要求,ECat初始化后默认不会上传数据!调用权限设置接口后才会上传数据
- 如果INTERNET权限需要用户授权,则在授权后调用(仅调用一次,下次打开应用后无需调用)
- 如果不需要用户授权,则在初始化后直接调用
ECat.SetPermission("INTERNET", true);
1.4.3 Native崩溃时获取Lua栈(支持安卓xLua和toLua)#
前提条件:
libxlua.so或libtolua.so中,必须导出luaL_traceback、lua_tolstring函数。
调用以下接口,lua栈将随Native崩溃上报,在平台异常详情userLog中展示。
//xLua使用说明:
//初始化后调用(需导入libECatLuaTrace.so)
//第一个参数:需传入xlua或tolua的全局luastate指针
//第二个参数:是否开启调试模式(打开后会在logcat打印崩溃时的lua栈,release环境应关闭)
ECat.RegisterLuaStackCallback(luaEnv.L, true);
//toLua使用说明:
//初始化后调用(需导入libJGetLuaStack.so)
//由于toLua源码中,luastate指针为protect类型,因此需要手动在LuaState.cs中添加Get接口:
public IntPtr GetStatePtr()
{
return L;
}
//-----------------
new LuaResLoader();
lua = new LuaState();
ECat.RegisterLuaStackCallback(lua.GetStatePtr(), false);
1.4.4 设置崩溃回调函数(非必须)#
如果希望在应用崩溃后,完成一些自定义任务,可以设置崩溃回调函数(当前支持安卓、iOS、Windows、鸿蒙平台),示例:
[MonoPInvokeCallback(typeof(ECat.CrashCallback))]
private static bool onCrash(string crashInfo)
{
Debug.LogWarning("crash call back:" + crashInfo);
return true;
}
//初始化后设置
ECat.SetCrashCallback(onCrash);
回调函数参数和返回值说明:
/// <summary>
/// 崩溃回调,支持安卓、iOS、鸿蒙崩溃,通过SetCrashCallback设置回调函数,可在崩溃后执行;
/// 崩溃函数需要添加声明:[MonoPInvokeCallback(typeof(ECat.CrashCallback))]
/// 由于崩溃回调会阻塞崩溃线程,因此若在回调中执行异步逻辑,可能会执行失败。
/// </summary>
///
/// <param name="crashInfo">
/// 崩溃回调的参数(json字符串),参数说明:
/// 示例:{"type":"iOSCrash","crashUuid":"7A9A5EB7-8705-4C68-BAAA-3C1DA35E3409"}
///
/// type:崩溃类型,取值范围:
/// "NativeCrash":Native崩溃
/// "JavaCrash":Java崩溃
/// "ANR":应用无响应
/// "iOSCrash":iOS崩溃
/// "JsCrash":鸿蒙Js崩溃
///
/// crashUuid:崩溃id,与对应异常数据的id相同
///
/// </param>
/// <returns>回调返回值,true代表回调成功</returns>
public delegate bool CrashCallback(string crashInfo);
1.4.5 获取应用上次退出信息(非必须)#
由于各种原因,难以保证崩溃后能百分之百成功执行回调函数。因此我们提供了获取应用上次退出信息的接口,帮助开发者在下次启动后,执行未完成的崩溃回调任务。(当前支持安卓、iOS、Windows、鸿蒙平台)
string exitInfo = ECat.getLastExitInfo();
返回值说明:
/// <summary>
/// 获取应用上次退出信息
/// </summary>
/// <returns>
/// 上次退出信息(json格式),示例:{"type":"","crashUuid":"","crashTime":0,"finishCrashCallback":false}
/// 字段说明:
///
/// type:退出类型。取值范围:
/// "":无崩溃
/// "NativeCrash":Native崩溃
/// "JavaCrash":Java崩溃
/// "ANR":应用无响应
/// "iOSCrash":iOS崩溃
/// "BlockCrash":iOS卡死崩溃
/// "FOOM":iOS FOOM
/// "JsCrash":鸿蒙Js崩溃
/// "Freeze":鸿蒙Freeze
///
/// crashUuid:崩溃id。若上次退出类型为崩溃,则为该崩溃数据的id
///
/// crashTime:崩溃时间(单位:毫秒)。若上次退出类型为崩溃,则为该崩溃的时间
///
/// finishCrashCallback:是否完成CrashCallback崩溃回调。若上次崩溃后,执行了回调函数并且回调函数返回值为true,则为true,否则为false
///
/// </returns>
public static string getLastExitInfo()
1.5 在导出工程中初始化(非必须)#
1.5.1 安卓、iOS、Windows、鸿蒙#
可以从Unity中导出对应平台的应用工程。在工程中,可直接使用对应平台SDK的API。
例如,在导出的Android Studio工程中直接使用安卓SDK的API,可参考Android SDK使用文档中的初始化章节。
C#层依然可以使用所有API,但必须调用InitECat()或者EnableExceptionHandler()完成C#层的初始化流程。
不管是在C#层初始化,还是在导出工程中初始化,仅首次初始化有效,因此需要注意初始化配置的有效性。
windows平台特别说明:
若在导出的VS工程中初始化,SDK中的dll需要与目标exe在同一目录;
必须删除Plugins中的dll,否则sdk会被加载两次,因内存不共享,会出现数据不一致问题。
鸿蒙平台特别说明:
若要在导出工程中初始化,必须在主线程中执行;
getLastExitInfo和GetCrashlyticsDeviceId的返回值为空字符串
1.5.2 微信小游戏#
目前,建议仅在C#层初始化SDK,若希望在js层和C#层使用SDK的API,可以用事件监听的方式实现,具体方案请联系我们。
1.6 符号文件上传#
有关符号文件的说明见《符号文件上传工具使用文档》
二、API说明#
2.1 ECat.Config类#
用于初始化时的配置
public sealed class Config
{
public string appVersion = ""; //应用版本
public string appChannel = ""; //渠道
public string userId = ""; //用户id
public string serverUrl = ""; //上传数据的url,为空时使用sdk内的默认值
public bool enableDebugMode = false; //是否启用调试模式(默认:false)
public LogSeverity reportLogLevel = LogSeverity.LogError; //Unity Log上报级别(默认:LogError)
public bool enablePrivateLogHandler = false; //是否启用私有UnityLogHandler(启用后Unity日志将不打印到控制台,默认:false)
public int lagThreshold = 5000; //卡顿阈值(单位:ms,有效值:2000-10000,默认:5000)
}
2.2 ECat类#
/// <summary>
/// 崩溃回调,支持安卓、iOS、鸿蒙崩溃,通过SetCrashCallback设置回调函数,可在崩溃后执行;
/// 崩溃函数需要添加声明:[MonoPInvokeCallback(typeof(ECat.CrashCallback))]
/// 由于崩溃回调会阻塞崩溃线程,因此若在回调中执行异步逻辑,可能会执行失败。
/// </summary>
///
/// <param name="crashInfo">
/// 崩溃回调的参数(json字符串),参数说明:
/// 示例:{"type":"iOSCrash","crashUuid":"7A9A5EB7-8705-4C68-BAAA-3C1DA35E3409"}
///
/// type:崩溃类型,取值范围:
/// "NativeCrash":Native崩溃
/// "JavaCrash":Java崩溃
/// "ANR":应用无响应
/// "iOSCrash":iOS崩溃
/// "JsCrash":鸿蒙Js崩溃
///
/// crashUuid:崩溃id,与对应异常数据的id相同
///
/// </param>
/// <returns>回调返回值,true代表回调成功</returns>
public delegate bool CrashCallback(string crashInfo);
/// <summary>
/// 初始化
/// </summary>
/// <param name="appId">平台分配的AppId</param>
/// <param name="config">初始化配置</param>
public static void InitECat(string appId, Config config);
/// <summary>
/// 上报捕获的C#异常
/// </summary>
/// <param name="e">E.</param>
/// <param name="message">Message.</param>
public static void ReportException(Exception e, string message);
/// <summary>
/// 上报自定义异常
/// </summary>
/// <param name="name">Name.</param>
/// <param name="message">Message.</param>
/// <param name="stackTrace">Stack trace.</param>
public static void ReportException(string name, string message, string stackTrace);
/// <summary>
/// 用于上传Unity.Debug日志,可在自己实现的ILogHandler.LogFormat接口中调用
/// </summary>
/// <param name="logType"></param>
/// <param name="logMessage"></param>
public static void ReportUnityLog(LogType logType, string logMessage);
/// <summary>
/// 设置用户Id
/// </summary>
/// <param name="userId">User identifier.</param>
public static void SetUserId(string userId);
/// <summary>
/// 测试Native崩溃
/// </summary>
public static void TestNativeCrash();
/// <summary>
/// 注册Native异常时的LuaStack回调,以便在Native崩溃时获取当时的lua栈。
/// </summary>
/// <param name="L">xLua所使用的全局lua_State *L。</param>
/// <param name="debugMode">是否开启LuaStack native层的调试模式</param>
public static void RegisterLuaStackCallback(IntPtr L, bool debugMode);
/// <summary>
/// 设置场景标签
/// </summary>
/// <param name="sceneId">Scene identifier.</param>
public static void SetSceneTag(int sceneId);
/// <summary>
/// 设置Permission是否有效
/// </summary>
/// <param name="permission">安卓目前只支持 "INTERNET" / "READ_LOGS", 鸿蒙只支持"INTERNET".</param>
/// <param name="b">是否有效.</param>
public static void SetPermission(string permission, bool b);
/// <summary>
/// 增加自定义键值对,异常时上传
/// </summary>
/// <param name="key">Key.</param>
/// <param name="value">Value.</param>
public static void AddUserData(string key, string value);
/// <summary>
/// 获取SDK内部设备id
/// </summary>
/// <returns></returns>
public static string GetECatDeviceId();
/// <summary>
/// 设置应用版本
/// </summary>
/// <param name="appVersion">appVersion.</param>
public static void SetAppVersion(string appVersion);
/// <summary>
/// 设置卡顿阈值, 2000至10000, 默认值:5000, 单位:ms
/// </summary>
/// <param name="threshold"></param>
public static void SetLagThreshold(int threshold);
/// <summary>
/// 停止检测卡顿
/// </summary>
/// <param name="duration">停止持续时间(单位:秒),duration > 0时,经过duration时间后卡顿检测再次开启,否则不再开启</param>
public static void StopLagMonitor(int duration)
/// <summary>
/// 开启卡顿检测
/// </summary>
public static void StartLagMonitor()
/// <summary>
/// 开启所有异常检测
/// </summary>
public static void StartAllMonitor();
/// <summary>
/// 停止所有异常检测
/// </summary>
public static void StopAllMonitor();
/// <summary>
/// 设置崩溃回调函数
/// </summary>
/// <param name="callback"></param>
public static void SetCrashCallback(CrashCallback callback);
/// <summary>
/// 获取应用上次退出信息
/// </summary>
/// <returns>
/// 上次退出信息(json格式),示例:{"type":"","crashUuid":"","crashTime":0,"finishCrashCallback":false}
/// 字段说明:
///
/// type:退出类型。取值范围:
/// "":无崩溃
/// "NativeCrash":Native崩溃
/// "JavaCrash":Java崩溃
/// "ANR":应用无响应
/// "iOSCrash":iOS崩溃
/// "BlockCrash":iOS卡死崩溃
/// "FOOM":iOS FOOM
/// "JsCrash":鸿蒙Js崩溃
/// "Freeze":鸿蒙Freeze
///
/// crashUuid:崩溃id。若上次退出类型为崩溃,则为该崩溃数据的id
///
/// crashTime:崩溃时间(单位:毫秒)。若上次退出类型为崩溃,则为该崩溃的时间
///
/// finishCrashCallback:是否完成CrashCallback崩溃回调。若上次崩溃后,执行了回调函数并且回调函数返回值为true,则为true,否则为false
///
/// </returns>
public static string getLastExitInfo();
/// <summary>
/// 通知应用即将退出。仅在iOS平台有效,退出应用前调用此接口,以减少FOOM误报
/// </summary>
public static void NotifyAppWillQuit();
三、FAQ#
-
某些C#异常和LogError为什么没有lua栈?
使用xlua后,C#抛出异常时,xlua会将lua栈附加到异常对象的StackTrace中。在C#层使用try catch捕获到的异常不含lua栈,因此需要重新用throw关键字抛出。示例:
try { //... } catch (Exception e) { //... throw (e); //有lua栈 //UnityEngine.Debug.LogError(e.ToString()); //没有lua栈 //ECat.ReportException(e, "luaTest"); //没有lua栈 } -
用IL2CPP编译时,C#异常栈为什么没有文件名和行号?
需要升级Unity Editor至2023.1以上版本并做相应配置,详情可见Unity官方文档:
https://docs.unity3d.com/2023.1/Documentation/Manual/IL2CPP-managed-stack-traces.html
-
ECat能否获取安卓系统日志?
由于读取系统日志是敏感权限,需要应用获得用户授权后才能读取。
与INTERNET权限类似,获取READ_LOGS权限后,调用SetAndroidPermission("READ_LOGS", true)接口,系统日志将随异常上报。
-
内存泄漏检测性能开销有多少?
基于KOOM,整体性能损耗 < 5%,建议在出现疑似内存泄漏问题时,在性能较好的设备上开启
-
调用Application.Quit()后出现崩溃,该如何屏蔽?
我们发现,在Unity 2019的一些版本上,调用Application.Quit()退出应用的过程中,可能出现因Unity的jni对象释放导致的崩溃。但此崩溃对用户是无感知的,Unity也会忽略此崩溃信号。
如果希望ECat不要上报此崩溃,可以在Application.Quit()前调用ECat.StopCrashMonitor(),停止native崩溃检测:
//以下代码出现崩溃数据 Application.OpenURL("https://www.baidu.com"); Application.Quit(); //屏蔽退出过程中的崩溃 Application.OpenURL("https://www.baidu.com"); ECat.StopCrashMonitor(); Application.Quit(); -
鸿蒙SDK初始化后,立即调用其他API,为何失效?
鸿蒙SDK主要用ArkTS实现,初始化接口中有些系统API只能异步调用,因此初始化接口为一个异步函数。当前,团结引擎从C#调用ArkTS时,没有提供回调或Promise之类的异步机制,因此在初始化后调用其他接口,可能初始化还未完成。
当前,我们建议在较后的时机,调用其他API。经过我们测试,初始化后,异步函数完成大概在200ms以内。
除此之外,AddUserData接口即便不初始化也可以使用。
-
为什么有些相同的Unity Log不间断地上报?
除Editor SDK外的所有SDK都有异常上报抑制机制。针对Unity Log,我们将log和栈内容作为判断此异常是否已上传的依据。
当log内容中出现时间戳或随机字符串时,SDK无法判断此异常是否已经出现过。
因此,我们强烈建议,log内容中不要带有时间戳、用户id等不确定性很高的内容,这些字段已经在ECat的数据中,应减少冗余信息。
-
如何实现不打印Unity Debug日志到控制台,但能上传log到后台?
1)可以在初始化时,配置enablePrivateLogHandler为true,ECat将替换Unity的默认logHandler对象。
2)若项目中有自定义的ILogHandler实现,可在自实现的LogException和LogFormat接口中,分别调用ReportException和ReportUnityLog接口(可参考ECat.LogHandler类)。
请注意,由于Unity的unityLogger是静态对象,以上两种方式是互斥的。
-
在Unity子线程中,如何使用ECat接口?
安卓平台下,在子线程中调用ECat接口之前,需要在子线程调用AndroidJNI.AttachCurrentThread()